/*
* EASYDROPDOWN - A Drop-down Builder for Styleable Inputs and Menus
* Version: 2.1.3
* License: Creative Commons Attribution 3.0 Unported - CC BY 3.0
* http://creativecommons.org/licenses/by/3.0/
* This software may be used freely on commercial and non-commercial projects with attribution to the author/copyright holder.
* Author: Patrick Kunka
* Copyright 2013 Patrick Kunka, All Rights Reserved
*/

(function (root, factory) {

    if (typeof define === 'function' && define.amd) {

        // AMD
        define(['jquery'], factory);

    } else if (typeof module === 'object' && module.exports) {

        // Node, CommonJS之类的
        module.exports = factory(require('jquery'));

    } else {

        // 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery);

    }

}(this, function ($) {
  
  function EasyDropDown(){
    this.isField = true,
    this.down = false,
    this.inFocus = false,
    this.disabled = false,
    this.cutOff = false,
    this.hasLabel = false,
    this.keyboardMode = false,
    this.nativeTouch = true,
    this.wrapperClass = 'dropdown',
    this.onChange = null;
  };
  
  EasyDropDown.prototype = {
    constructor: EasyDropDown,
    instances: [],
    init: function(domNode, settings){
      var self = this;
      
      $.extend(self, settings);
      self.$select = $(domNode);
      self.id = domNode.id;
      self.options = [];
      self.$options = self.$select.find('option');
      self.isTouch = 'ontouchend' in document;
      self.$select.removeClass(self.wrapperClass+' dropdown');
      if(self.$select.is(':disabled')){
        self.disabled = true;
      };
      if(self.$options.length){
        self.$options.each(function(i){
          var $option = $(this);
          if($option.is(':selected')){
            self.selected = {
              index: i,
              title: $option.text()
            }
            self.focusIndex = i;
          };
          if($option.hasClass('label') && i == 0){
            self.hasLabel = true;
            self.label = $option.text();
            $option.attr('value','');
          } else {
            self.options.push({
              domNode: $option[0],
              title: $option.text(),
              value: $option.val(),
              selected: $option.is(':selected')
            });
          };
        });
        if(!self.selected){
          self.selected = {
            index: 0,
            title: self.$options.eq(0).text()
          }
          self.focusIndex = 0;
        };
        self.render();
      };
    },
  
    render: function(){
      var self = this,
        touchClass = self.isTouch && self.nativeTouch ? ' touch' : '',
        disabledClass = self.disabled ? ' disabled' : '';
      
      self.$container = self.$select.wrap('<div class="'+self.wrapperClass+touchClass+disabledClass+'"><span class="old"/></div>').parent().parent();
      self.$active = $('<span class="selected">'+self.selected.title+'</span>').appendTo(self.$container);
      self.$carat = $('<span class="carat"/>').appendTo(self.$container);
      self.$scrollWrapper = $('<div><ul/></div>').appendTo(self.$container);
      self.$dropDown = self.$scrollWrapper.find('ul');
      self.$form = self.$container.closest('form');
      $.each(self.options, function(){
        var option = this,
          active = option.selected ? ' class="active"':'';
        self.$dropDown.append('<li'+active+'>'+option.title+'</li>');
      });
      self.$items = self.$dropDown.find('li');
      self.maxHeight = 0;
      if(self.cutOff && self.$items.length > self.cutOff)self.$container.addClass('scrollable');
      for(i = 0; i < self.$items.length; i++){
        var $item = self.$items.eq(i);
        self.maxHeight += $item.outerHeight();
        if(self.cutOff == i+1){
          break;
        };
      };

      if(self.isTouch && self.nativeTouch){
        self.bindTouchHandlers();
      } else {
        self.bindHandlers();
      };
    },
    
    bindTouchHandlers: function(){
      var self = this;
      self.$container.on('click.easyDropDown',function(){
        self.$select.focus();
      });
      self.$select.on({
        change: function(){
          var $selected = $(this).find('option:selected'),
            title = $selected.text(),
            value = $selected.val();
            
          self.$active.text(title);
          if(typeof self.onChange === 'function'){
            self.onChange.call(self.$select[0],{
              title: title, 
              value: value
            });
          };
        },
        focus: function(){
          self.$container.addClass('focus');
        },
        blur: function(){
          self.$container.removeClass('focus');
        }
      });
    },
  
    bindHandlers: function(){
      var self = this;
      self.query = '';
      self.$container.on({
        'click.easyDropDown': function(){
          if(!self.down && !self.disabled){
            self.open();
          } else {
            self.close();
          };
        },
        'mousemove.easyDropDown': function(){
          if(self.keyboardMode){
            self.keyboardMode = false;
          };
        }
      });
      
      $('body').on('click.easyDropDown.'+self.id,function(e){
        var $target = $(e.target),
          classNames = self.wrapperClass.split(' ').join('.');

        if(!$target.closest('.'+classNames).length && self.down){
          self.close();
        };
      });

      self.$items.on({
        'click.easyDropDown': function(){
          var index = $(this).index();
          self.select(index);
          self.$select.focus();
        },
        'mouseover.easyDropDown': function(){
          if(!self.keyboardMode){
            var $t = $(this);
            $t.addClass('focus').siblings().removeClass('focus');
            self.focusIndex = $t.index();
          };
        },
        'mouseout.easyDropDown': function(){
          if(!self.keyboardMode){
            $(this).removeClass('focus');
          };
        }
      });

      self.$select.on({
        'focus.easyDropDown': function(){
          self.$container.addClass('focus');
          self.inFocus = true;
        },
        'blur.easyDropDown': function(){
          self.$container.removeClass('focus');
          self.inFocus = false;
        },
        'keydown.easyDropDown': function(e){
          if(self.inFocus){
            self.keyboardMode = true;
            var key = e.keyCode;

            if(key == 38 || key == 40 || key == 32){
              e.preventDefault();
              if(key == 38){
                self.focusIndex--
                self.focusIndex = self.focusIndex < 0 ? self.$items.length - 1 : self.focusIndex;
              } else if(key == 40){
                self.focusIndex++
                self.focusIndex = self.focusIndex > self.$items.length - 1 ? 0 : self.focusIndex;
              };
              if(!self.down){
                self.open();
              };
              self.$items.removeClass('focus').eq(self.focusIndex).addClass('focus');
              if(self.cutOff){
                self.scrollToView();
              };
              self.query = '';
            };
            if(self.down){
              if(key == 9 || key == 27){
                self.close();
              } else if(key == 13){
                e.preventDefault();
                self.select(self.focusIndex);
                self.close();
                return false;
              } else if(key == 8){
                e.preventDefault();
                self.query = self.query.slice(0,-1);
                self.search();
                clearTimeout(self.resetQuery);
                return false;
              } else if(key != 38 && key != 40){
                var letter = String.fromCharCode(key);
                self.query += letter;
                self.search();
                clearTimeout(self.resetQuery);
              };
            };
          };
        },
        'keyup.easyDropDown': function(){
          self.resetQuery = setTimeout(function(){
            self.query = '';
          },1200);
        }
      });
      
      self.$dropDown.on('scroll.easyDropDown',function(e){
        if(self.$dropDown[0].scrollTop >= self.$dropDown[0].scrollHeight - self.maxHeight){
          self.$container.addClass('bottom');
        } else {
          self.$container.removeClass('bottom');
        };
      });
      
      if(self.$form.length){
        self.$form.on('reset.easyDropDown', function(){
          var active = self.hasLabel ? self.label : self.options[0].title;
          self.$active.text(active);
        });
      };
    },
    
    unbindHandlers: function(){
      var self = this;
      
      self.$container
        .add(self.$select)
        .add(self.$items)
        .add(self.$form)
        .add(self.$dropDown)
        .off('.easyDropDown');
      $('body').off('.'+self.id);
    },
    
    open: function(){
      var self = this,
        scrollTop = window.scrollY || document.documentElement.scrollTop,
        scrollLeft = window.scrollX || document.documentElement.scrollLeft,
        scrollOffset = self.notInViewport(scrollTop);

      self.closeAll();
      self.$select.focus();
      window.scrollTo(scrollLeft, scrollTop+scrollOffset);
      self.$container.addClass('open');
      self.$scrollWrapper.css('height',self.maxHeight+'px');
      self.down = true;
    },
    
    close: function(){
      var self = this;
      self.$container.removeClass('open');
      self.$scrollWrapper.css('height','0px');
      self.focusIndex = self.selected.index;
      self.query = '';
      self.down = false;
    },
    
    closeAll: function(){
      var self = this,
        instances = Object.getPrototypeOf(self).instances;
      for(var key in instances){
        var instance = instances[key];
        instance.close();
      };
    },
  
    select: function(index){
      var self = this;
      
      if(typeof index === 'string'){
        index = self.$select.find('option[value='+index+']').index() - 1;
      };
      
      var option = self.options[index],
        selectIndex = self.hasLabel ? index + 1 : index;
      self.$items.removeClass('active').eq(index).addClass('active');
      self.$active.text(option.title);
      self.$select
        .find('option')
        .removeAttr('selected')
        .eq(selectIndex)
        .prop('selected',true)
        .parent()
        .trigger('change');
        
      self.selected = {
        index: index,
        title: option.title
      };
      self.focusIndex = i;
      if(typeof self.onChange === 'function'){
        self.onChange.call(self.$select[0],{
          title: option.title, 
          value: option.value
        });
      };
    },
    
    search: function(){
      var self = this,
        lock = function(i){
          self.focusIndex = i;
          self.$items.removeClass('focus').eq(self.focusIndex).addClass('focus');
          self.scrollToView();  
        },
        getTitle = function(i){
          return self.options[i].title.toUpperCase();
        };
        
      for(i = 0; i < self.options.length; i++){
        var title = getTitle(i);
        if(title.indexOf(self.query) == 0){
          lock(i);
          return;
        };
      };
      
      for(i = 0; i < self.options.length; i++){
        var title = getTitle(i);
        if(title.indexOf(self.query) > -1){
          lock(i);
          break;
        };
      };
    },
    
    scrollToView: function(){
      var self = this;
      if(self.focusIndex >= self.cutOff){
        var $focusItem = self.$items.eq(self.focusIndex),
          scroll = ($focusItem.outerHeight() * (self.focusIndex + 1)) - self.maxHeight;
      
        self.$dropDown.scrollTop(scroll);
      };
    },
    
    notInViewport: function(scrollTop){
      var self = this,
        range = {
          min: scrollTop,
          max: scrollTop + (window.innerHeight || document.documentElement.clientHeight)
        },
        menuBottom = self.$dropDown.offset().top + self.maxHeight;
        
      if(menuBottom >= range.min && menuBottom <= range.max){
        return 0;
      } else {
        return (menuBottom - range.max) + 5;
      };
    },
    
    destroy: function(){
      var self = this;
      self.unbindHandlers();
      self.$select.unwrap().siblings().remove();
      self.$select.unwrap();
      delete Object.getPrototypeOf(self).instances[self.$select[0].id];
    },
    
    disable: function(){
      var self = this;
      self.disabled = true;
      self.$container.addClass('disabled');
      self.$select.attr('disabled',true);
      if(!self.down)self.close();
    },
    
    enable: function(){
      var self = this;
      self.disabled = false;
      self.$container.removeClass('disabled');
      self.$select.attr('disabled',false);
    }
  };
  
  var instantiate = function(domNode, settings){
      domNode.id = !domNode.id ? 'EasyDropDown'+rand() : domNode.id;
      var instance = new EasyDropDown();
      if(!instance.instances[domNode.id]){
        instance.instances[domNode.id] = instance;
        instance.init(domNode, settings);
      };
    },
    rand = function(){
      return ('00000'+(Math.random()*16777216<<0).toString(16)).substr(-6).toUpperCase();
    };
  
  $.fn.easyDropDown = function(){
    var args = arguments,
      dataReturn = [],
      eachReturn;
      
    eachReturn = this.each(function(){
      if(args && typeof args[0] === 'string'){
        var data = EasyDropDown.prototype.instances[this.id][args[0]](args[1], args[2]);
        if(data)dataReturn.push(data);
      } else {
        instantiate(this, args[0]);
      };
    });
    
    if(dataReturn.length){
      return dataReturn.length > 1 ? dataReturn : dataReturn[0];
    } else {
      return eachReturn;
    };
  };
  
  $(function(){
    if(typeof Object.getPrototypeOf !== 'function'){
      if(typeof 'test'.__proto__ === 'object'){
        Object.getPrototypeOf = function(object){
          return object.__proto__;
        };
      } else {
        Object.getPrototypeOf = function(object){
          return object.constructor.prototype;
        };
      };
    };
    
    $('select.dropdown').each(function(){
      var json = $(this).attr('data-settings');
        settings = json ? $.parseJSON(json) : {}; 
      instantiate(this, settings);
    });
  });
}));